package com.google.code.b0rx0r;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.google.code.b0rx0r.advancedSamplerEngine.AudioProcessor;
import com.google.code.b0rx0r.advancedSamplerEngine.Enqueueable;
import com.google.code.b0rx0r.advancedSamplerEngine.AudioProcessor.EnqueuedEnqueueable;
import com.google.code.b0rx0r.advancedSamplerEngine.effect.AmplitudeEffect;
import com.google.code.b0rx0r.advancedSamplerEngine.effect.LoopEffect;
import com.google.code.b0rx0r.advancedSamplerEngine.effect.PlaybackSpeedEffect;
import com.google.code.b0rx0r.advancedSamplerEngine.effect.SliceEffect;
import com.google.code.b0rx0r.advancedSamplerEngine.effect.StartpointEffect;
import com.google.code.b0rx0r.advancedSamplerEngine.effect.modulation.EnvelopeModulation;
import com.google.code.b0rx0r.advancedSamplerEngine.effect.modulation.FixedModulation;
import com.google.code.b0rx0r.advancedSamplerEngine.source.MemorySample;
import com.google.code.b0rx0r.program.OffsetSampleDataContainer;
import com.google.code.b0rx0r.program.ProgramManager;
import com.google.code.b0rx0r.program.Sample;
import com.google.code.b0rx0r.program.SampleDataContainer;
import com.google.code.b0rx0r.program.Sequencable;


/** The play queue is the place where playables are enqueued for playing. 
 *  The vst interface and gui place them here, the AudioProcessor reads them from here.
 *  
 * @author Lui
 *
 */
public class PlayQueue {
	private ProgramManager programManager;
	private AudioProcessor processor;
	
	private List<Learner> learners = new ArrayList<Learner>();
	
	public PlayQueue(ProgramManager programManager, AudioProcessor processor) {
		this.programManager = programManager;
		this.processor = processor;
	}

	public void addLearner(Learner learner) {
		learners.add(learner);
	}
	
	public void removeLearner(Learner learner) {
		learners.remove(learner);
	}
	
	public boolean fireLearnNote(int note) {
		boolean hadALearner = false;
		for (Learner learner : learners) {
			boolean learned = learner.learnNote(note);
			if (learned) {
				hadALearner = true;
			}
		}
		
		return hadALearner;
	}
	
	public void fireLearnEndNote(int note) {
		for (Learner learner : learners) {
			learner.endNote(note);
		}
	}

	public void noteOn(int note, int velocity) {
		boolean learned = fireLearnNote(note);
		
		if (!learned) {
		  Collection<Sequencable> ss = programManager.getProgram().getSequencablesForMidiNote(note);
		  
		  for (Sequencable s : ss)
		    enqueueSequencable(s,velocity);
		}
	}
	
	public void enqueueSequencable(Sequencable s, int velocity) {
		for (OffsetSampleDataContainer osdc : s.getSampleData()) {
			processor.enqueue(makeEnqueueable(osdc.getSampleDataContainer(), velocity), processor.getTime() + osdc.getOffset());
		}
	}
	



	public Stoppable enqueueSequencableLooping(Sequencable s, int velocity) {
		if (s.getSampleData().size() > 1)
			throw new IllegalArgumentException("cannot loop a sequencable with more than one sample data container");
		
		OffsetSampleDataContainer osdc = s.getSampleData().iterator().next();
		Enqueueable e = makeEnqueueable(osdc.getSampleDataContainer(), velocity);
		
		e = new LoopEffect(e);
	  EnqueuedEnqueueable ee = processor.enqueue(e, processor.getTime() + osdc.getOffset());

		return new Stoppable(ee, processor);
	}


	private Enqueueable makeEnqueueable(SampleDataContainer sdc, int velocity) {
		int length = sdc.getEndOffset() - sdc.getStartOffset();
		float playbackSpeed = sdc.getPlaybackSpeed();
		
		Enqueueable sampleData = new MemorySample(sdc.getFloatSampleBuffer(), sdc.getOutputMap());
		sampleData = new SliceEffect(sampleData, sdc.getStartOffset(), length);
		
		float fVolume = ((float)velocity)/127f;
		sampleData = new AmplitudeEffect(sampleData).setModulation(new EnvelopeModulation()
		                                                                 .addPoint(0, 0f)
		                                                                 .addPoint(100, fVolume)
		                                                                 .addPoint(length - 101, fVolume)
		                                                                 .addPoint(length - 1, 0f));
		if (playbackSpeed != 1f) {
		  sampleData = new PlaybackSpeedEffect(sampleData).setModulation(new FixedModulation(playbackSpeed));
		}
		return sampleData;
	}

	public void noteOff(int note) {
		fireLearnEndNote(note);
	}

	public void allNotesOff() {
	}


	
	
	public static class EnqueuedSampleDataContainer {
		private SampleDataContainer sequencable;
		private long start;
		private int velocity;
		
		public EnqueuedSampleDataContainer(SampleDataContainer sequencable, long start, int velocity) {
			this.sequencable = sequencable;
			this.start = start;
			this.velocity = velocity;
		}

		public SampleDataContainer getSequencable() {
			return sequencable;
		}

		public long getStart() {
			return start;
		}

		public int getVelocity() {
			return velocity;
		}
		
		
		
		
		
	}




}

